﻿using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace D.UtilCore.JwtUtil
{

    /// <summary>
    /// Jwt授权认证
    /// </summary>
    public class JwtService : IJwtService
    {
        /// <summary>
        /// 生成token
        /// </summary>
        /// <param name="tokenModel"></param>
        /// <returns></returns>
        public string IssueToken(TokenModel tokenModel)
        {
            var dateTime = DateTime.Now;

            Console.WriteLine("------------------jwtIssue:dateTime-------------------");
            Console.WriteLine(dateTime.ToString("yyyy-MM-dd HH:mm:ss"));
            Console.WriteLine("-------------------------------------");


            var expires = dateTime.AddMinutes(tokenModel.Expires);
            Console.WriteLine("------------------jwtIssue-expires-------------------");
            Console.WriteLine(expires.ToString("yyyy-MM-dd HH:mm:ss"));
            Console.WriteLine("-------------------------------------");

            var claims = new List<Claim>()
            {
                new Claim("ProjectName", tokenModel.ProjectName),
                new Claim("Account", tokenModel.Account),
                new Claim(JwtRegisteredClaimNames.Jti,tokenModel.Id),
                new Claim(JwtRegisteredClaimNames.Iss,tokenModel.Issuer),
                new Claim(JwtRegisteredClaimNames.Aud,tokenModel.Audience),
                new Claim(JwtRegisteredClaimNames.Iat, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
                new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") ,
                //这个就是过期时间，目前是过期100秒，可自定义，注意JWT有自己的缓冲过期时间
                new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(expires).ToUnixTimeSeconds()}"),
            };

            if (!string.IsNullOrEmpty(tokenModel.NickName))
            {
                claims.Add(new Claim("NickName", tokenModel.NickName));
            }
            if (!string.IsNullOrEmpty(tokenModel.RoleIds))
            {
                claims.Add(new Claim("RoleId", tokenModel.RoleIds));
            }

            // 这个Role是官方UseAuthentication要要验证的Role，我们就不用手动设置Role这个属性了 一个用户多个角色
            claims.AddRange(tokenModel.RoleIds.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));

            // 秘钥 (SymmetricSecurityKey 对安全性的要求，密钥的长度太短会报出异常)
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenModel.SecretKey));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            // 用于展示加密、签名时的详细错误信息
            Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;

            var jwtHandler = new JwtSecurityTokenHandler();
            var token = jwtHandler.CreateEncodedJwt(new SecurityTokenDescriptor
            {
                Issuer = tokenModel.Issuer,
                Audience = tokenModel.Audience,
                SigningCredentials = creds,
                Subject = new ClaimsIdentity(claims, JwtBearerDefaults.AuthenticationScheme),
                NotBefore = DateTime.UtcNow,//token不能早于这个时间使用
                Expires = DateTime.UtcNow.AddMinutes(tokenModel.Expires),//添加过期时间
            });
            return token;
        }

        /// <summary>
        /// 解析token
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        public TokenModel SerializeToken(string token)
        {
            var jwtHandler = new JwtSecurityTokenHandler();
            JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(token);
            object roleId = new object();
            object nickname = new object();
            object account = new object();
            object issuer = new object();
            try
            { 
                jwtToken.Payload.TryGetValue("RoleId", out roleId);
                jwtToken.Payload.TryGetValue("NickName", out nickname);
                jwtToken.Payload.TryGetValue("Account", out account);
                jwtToken.Payload.TryGetValue(JwtRegisteredClaimNames.Iss, out issuer); 
            }
            catch (Exception e)
            {
                return null;
            }

            return new TokenModel
            {
                Id = jwtToken.Id,
                RoleIds = roleId?.ToString(),
                NickName = nickname?.ToString(),
                Account = account?.ToString(),
                Issuer = issuer?.ToString(),
            };
        }

        /// <summary>
        /// 验证token
        /// </summary>
        /// <param name="token"></param>
        /// <param name="issuer"></param>
        /// <param name="secretKey"></param>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        public string ValidateToken(string token, string issuer, string secretKey, out DateTime dateTime)
        {
            dateTime = DateTime.Now;
            var principal = GetPrincipal(token, issuer, secretKey, out dateTime);

            if (principal == null)
                return default(string);

            ClaimsIdentity identity = null;
            try
            {
                identity = (ClaimsIdentity)principal.Identity;
            }
            catch (NullReferenceException)
            {
                return null;
            }
            return identity.FindFirst("Account").Value;
        }

        /// <summary>
        /// 从Token中得到ClaimsPrincipal对象
        /// </summary>
        /// <param name="token"></param>
        /// <param name="dateTime"></param>
        /// <param name="issuer"></param>
        /// <param name="secretKey"></param>
        /// <returns></returns>
        private ClaimsPrincipal GetPrincipal(string token, string issuer, string secretKey, out DateTime dateTime)
        {
            dateTime = DateTime.Now;

            try
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var jwtToken = (JwtSecurityToken)tokenHandler.ReadToken(token);

                if (jwtToken == null)
                    return null;

                var parameters = new TokenValidationParameters()
                {
                    RequireExpirationTime = true,
                    ValidateIssuer = true,//验证创建该令牌的发布者
                    ValidateLifetime = true,//检查令牌是否未过期，以及发行者的签名密钥是否有效
                    ValidateAudience = false,//确保令牌的接收者有权接收它
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)),
                    ValidIssuer = issuer, //验证创建该令牌的发布者
                    //ClockSkew = TimeSpan.FromSeconds(0)//缓冲过期时间，总的有效时间等于这个时间加上jwt的过期时间
                };
                //验证token 
                var principal = tokenHandler.ValidateToken(token, parameters, out var securityToken);
                //若开始时间大于当前时间 或结束时间小于当前时间 则返回空
                if (securityToken.ValidFrom.ToLocalTime() > DateTime.Now || securityToken.ValidTo.ToLocalTime() < DateTime.Now)
                {
                    dateTime = DateTime.Now;
                    return null;
                }
                dateTime = securityToken.ValidTo.ToLocalTime();//返回Token结束时间
                return principal;
            }
            catch (SecurityTokenExpiredException stee)
            {
                //表示过期
                throw new Exception("Token过期");
            }
            catch (SecurityTokenException ste)
            {
                //表示token错误
                throw new Exception("Token错误");
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

    }
}
